﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using AgileTimeTracker.Domain.Common;
using AgileTimeTracker.Domain.Exceptions;
using Cwi.Utils;

namespace AgileTimeTracker.Infra.Repository.File
{
    public class BaseFileRepository<T> : IRepository<T> where T : BaseEntity
    {
        private readonly FileInfo dataFile;

        private List<T> data;

        private List<T> Data
        {
            get
            {
                if (this.data == null)
                {
                    this.data = this.ReadData() ?? new List<T>();
                }
                
                return this.data;
            }

            set
            {
                this.data = value;
            }
        }

        public BaseFileRepository(FileInfo dataFile)
        {
            Check.NotNull(dataFile, "dataFile");
            this.dataFile = dataFile;
        }

        public BaseFileRepository(string dataFile)
            : this(new FileInfo(dataFile))
        {
        }

        public T GetById(object id)
        {
            return this.Data.FirstOrDefault(t => t.Id == id);
        }

        public IQueryable<T> Query()
        {
            return this.Data.AsQueryable();
        }

        public void Add(T item)
        {
            if (this.Data.Any(t => t.Id == item.Id))
            {
                throw new RepositoryException("An item with the same Id was already added");
            }

            this.Data.Add(item);
        }

        public void Remove(T item)
        {
            var itemToRemove = this.Data.FirstOrDefault(t => t.Id == item.Id);

            if (itemToRemove != null)
            {
                this.Data.Remove(itemToRemove);
            }
        }

        public void Update(T item)
        {
            var oldItem = this.Data.FirstOrDefault(t => t.Id == item.Id);

            if (oldItem == null)
            {
                throw new RepositoryException("Unable to update an non existing item.");
            }

            var oldItemIndex = this.Data.IndexOf(oldItem);

            this.Data.RemoveAt(oldItemIndex);
            this.Data.Insert(oldItemIndex, item);
        }

        public void Commit()
        {
            foreach (var item in this.Data.Where(t => t.Id == null))
            {
                item.Id = Guid.NewGuid().ToString();
            }

            using (var stream = this.dataFile.Open(FileMode.Create, FileAccess.Write))
            {
                var serializer = new BinaryFormatter();
                serializer.Serialize(stream, this.data);
            }
        }

        private List<T> ReadData()
        {
            if (!this.dataFile.Exists)
            {
                return null;
            }

            using (var stream = this.dataFile.Open(FileMode.Open, FileAccess.Read))
            {
                var serializer = new BinaryFormatter();
                return serializer.Deserialize(stream) as List<T>;
            }
        }
    }
}
